<?php

namespace pmvc\util;

use pmvc\ClassLoader;
use pmvc\FileSystemClassLoader;
use pmvc\aop\ProxyUtil;
use ReflectionClass;

class ClassUtil {

	const REFLECTION_CACHE_KEY = "pmvc_util_ClassUtil_refCache";

	private static $LOADED_CLASS_CACHE		= Array();
	private static $CLASS_ISA_CACHE			= Array();
	private static $CLASS_LOADERS			= Array();
	private static $REFLECTION_SAVE_CB		= NULL;
	private static $REFLECTION_GET_CB		= NULL;

	public static function setReflectionCache($cacheData) {
		if (!is_array($cacheData)) {
			throw new \Exception("Invalid reflection cache data set");
		}
		if (array_key_exists("classIsA", $cacheData)) {
			ClassUtil::$CLASS_ISA_CACHE = $cacheData["classIsA"];
		}
	}

	public static function getReflectionCache() {
		return Array(
			"classIsA" => ClassUtil::$CLASS_ISA_CACHE
		);
	}

	/**
	 * Loads a class by it's name returning a {@link ReflectionClass}
	 * for the loaded class.
	 * @param string $className
	 * @return ReflectionClass the loaded class
	 */
	public static function loadClass($className) {
		if (!array_key_exists($className, ClassUtil::$LOADED_CLASS_CACHE)) {
			ClassUtil::$LOADED_CLASS_CACHE[$className] = new ReflectionClass($className);
		}
		return ClassUtil::$LOADED_CLASS_CACHE[$className];
	}

	/**
	 * Uses all of the registered class loaders to require
	 * the given file.
	 * @param $fileName the file to load
	 */
	public static function requireFile($fileName, $once=FALSE) {
		for ($i=count(ClassUtil::$CLASS_LOADERS)-1; $i>=0; $i--) {
			if (ClassUtil::$CLASS_LOADERS[$i]["classLoader"]->requireFile($fileName, $once)) {
				return TRUE;
			}
		}
		return FALSE;
	}
	
	/**
	 * Checks to see if the given $className is a
	 * $classIsA (if it can be cast to the given type).
	 * @param string $className
	 * @param string $classIsA
	 */
	public static function classIsA($className, $classIsA) {
		if ($className==$classIsA) {
			return true;
		}
		if (!array_key_exists($className."_isA_".$classIsA, ClassUtil::$CLASS_ISA_CACHE)) {
			$clazz = ClassUtil::loadClass($className);
			$clazzIsA = ClassUtil::loadClass($classIsA);
			ClassUtil::$CLASS_ISA_CACHE[$className."_isA_".$classIsA] = ($clazzIsA->isInterface()) 
				? $clazz->implementsInterface($classIsA)
				: $clazz->isSubclassOf($classIsA);
		}
		return ClassUtil::$CLASS_ISA_CACHE[$className."_isA_".$classIsA];
	}
	
	/**
	 * Checks to see if the given object is a $class.
	 * (if it can be cast to the given type).
	 * @param mixed $obj
	 * @param string $class
	 */
	public static function isInstance($obj, $class) {
		if (!is_object($obj)) {
			return false;
		}
		if ($class!="pmvc\aop\Proxy" && ProxyUtil::isProxy($obj)) {
			return ClassUtil::isInstance(ProxyUtil::getProxiedObject($obj), $class);
		}
		return is_a($obj, $class);
	}
	
	/**
	 * Adds a {@link ClassLoader} to the class loading
	 * sub system.
	 * @param pmvc\ClassLoader $classLoader
	 */
	public static function addClassLoader(ClassLoader $classLoader) {
		$loader = Array(
			"classLoader" => $classLoader,
			"func" => function($className)use($classLoader) {
				return $classLoader->loadClass($className);
			}
		);
		array_push(ClassUtil::$CLASS_LOADERS, $loader);
		spl_autoload_register($loader["func"], FALSE);
	}
	
	/**
	 * Removes a {@link ClassLoader} from the class loading
	 * sub system.
	 * @param pmvc\ClassLoader $classLoader
	 */
	public static function removeClassLoader(ClassLoader $classLoader) {
		$newLoaders = Array();
		for ($i=0; $i<count(ClassUtil::$CLASS_LOADERS); $i++) {
			$loader = ClassUtil::$CLASS_LOADERS[$i];
			if ($loader["classLoader"]==$classLoader) {
				spl_autoload_unregister($loader["func"]);
			} else {
				array_push($newLoaders, $loader);
			}
		}
		ClassUtil::$CLASS_LOADERS = $newLoaders;
	}

}


?>